Skip to content

[ci] add coverage reporting workflow - #271

Draft
wangxiyu191 wants to merge 2 commits into
mainfrom
feat/ci-coverage
Draft

[ci] add coverage reporting workflow#271
wangxiyu191 wants to merge 2 commits into
mainfrom
feat/ci-coverage

Conversation

@wangxiyu191

@wangxiyu191 wangxiyu191 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add a dedicated coverage GitHub Actions workflow for Bazel LCOV collection
  • move local/CI coverage execution into tools/coverage/run_coverage.sh
  • generate full coverage, incremental coverage summary, JSON summary, and genhtml HTML output
  • upload LCOV/summary/HTML artifacts and post/update a fixed PR coverage comment
  • add a gcov JSON isolation wrapper and unit tests for the coverage report parser

Notes

  • coverage runs //kv_cache_manager/... and //integration_test/... with --config=debug --config=ci_fast
  • coverage does not enable --config=client; client coverage can be handled separately
  • setup-bazel Bazelisk/repository/disk caches are enabled; PRs only restore cache
  • coverage disk cache is saved and replaced only when rebuildDiskCache=true; scheduled-cache-rebuild dispatches this rebuild nightly on main
  • runner can be selected with workflow dispatch runs-on or COVERAGE_RUNS_ON
  • PR comment uses job-scoped issues: write; coverage jobs keep the default read/cache permissions

Validation

  • bash -n tools/coverage/run_coverage.sh tools/coverage/gcov_json_isolated.sh
  • PYTHONPATH=tools/coverage python3 -m unittest tools.coverage.coverage_report_test
  • YAML parse check for .github/workflows/coverage.yml and .github/workflows/scheduled-cache-rebuild.yml
  • git diff --check origin/main...HEAD
  • local smoke coverage run for //kv_cache_manager/common/test:LoopThreadTest, including HTML generation
  • workflow_dispatch coverage on aliyun-ecs-x64: https://github.com/alibaba/tair-kvcache/actions/runs/30899860220
    • lines: 76.3% (23966 / 31428)
    • functions: 83.8% (3919 / 4676)
    • branches: 40.9% (22327 / 54600)

@qoderai qoderai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR adds a well-structured coverage reporting pipeline. The overall design — YAML anchors for step reuse, a dedicated comment-pr-coverage job that only downloads artifacts (no code checkout), Python-based LCOV parsing with unit tests, and a gcov JSON isolation wrapper — is solid. A few issues need attention before merging:

Blockers

  • actions/upload-artifact@v6 and actions/download-artifact@v6 do not exist (latest is v4). The workflow will fail at runtime.

Security

  • Runtime download of unsigned jq and gh binaries with no checksum verification, executed in a step that holds GH_TOKEN. Pre-install in the image or use the gh binary already present on ubuntu-latest runners.
  • --privileged + full host-root mount (/:/host_root/) is broader than needed; scoping the mount or separating the disk-cleanup step would reduce the blast radius.

Logic

  • The links / workflow-run URL conditional in the PR comment script is broken: the workflow run link is appended unconditionally regardless of the branch taken (lines 255–263).
  • DA: hit counts within a single LCOV record are overwritten instead of accumulated, which can under-report coverage.
  • non_coverable (changed lines without LCOV data) could theoretically go negative; a max(0, ...) guard is advisable.

Observability

  • If the coverage job fails before the upload step, comment-pr-coverage silently skips. A fallback "coverage run failed" comment would make it easier to notice failures on PRs.

🤖 Generated by Qoder

Comment on lines +160 to +119
run: |
set -x
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -sLo /usr/local/bin/jq "https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-linux-${ARCH}" && chmod +x /usr/local/bin/jq

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Downloading jq and gh binaries at runtime over plain HTTP/redirected HTTPS introduces a supply-chain risk: there's no checksum or signature verification. If the GitHub release URL is compromised or the redirect is MITM'd, the binary that runs with GH_TOKEN access could exfiltrate the token or delete arbitrary caches.

Consider either:

  1. Pinning with a sha256 check: echo "<expected-sha256> /usr/local/bin/jq" | sha256sum -c
  2. Installing both tools once in the dev container image (they're already needed for CI maintenance work).
  3. Using the gh CLI that ships with GitHub-hosted runners (it's pre-installed on ubuntu-latest at /usr/bin/gh).

🤖 Generated by Qoder

Comment on lines +215 to +174
- *upload_coverage_artifacts_step

comment-pr-coverage:
if: ${{ always() && github.event_name == 'pull_request' && (needs.coverage-github.outputs.coverage-artifact-url != '' || needs.coverage-aliyun.outputs.coverage-artifact-url != '') }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment-pr-coverage job fires when always() is true and at least one artifact URL is non-empty. However, both coverage-github and coverage-aliyun are mutually exclusive by design (only one will run per workflow trigger), so needs.coverage-github.result and needs.coverage-aliyun.result will be 'skipped' for the job that didn't run.

always() prevents the job from being skipped when its dependencies were skipped, but the artifact URL check already handles the empty-URL case. One edge case: if the coverage job itself fails before the upload step, the artifact URL will be empty and the comment job silently skips. It may be worth adding a fallback comment (e.g. "coverage run failed") so the PR isn't left without feedback.


🤖 Generated by Qoder

Comment on lines +258 to +217
const links = artifactUrl
? `HTML report and raw LCOV: [coverage-report](${artifactUrl})`
: `Workflow run: [${context.runId}](${runUrl})`;
const body = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When artifactUrl is truthy, links is set to the artifact URL line, but Workflow run: [${context.runId}](${runUrl}) is then appended unconditionally as a separate element in body (line 263). This means the workflow run link is always included regardless of the links branch, which makes the conditional on line 255–257 misleading — the fallback case also ends up with both lines. Consider either removing the conditional and always including both, or removing the duplicate from body.


🤖 Generated by Qoder

Comment thread .github/workflows/coverage.yml
Comment thread .github/workflows/coverage.yml
container:
image: ghcr.io/alibaba/tair-kvcache-kvcm-dev:2026_02_13_12_03_24230b1
volumes:
- /:/host_root/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mounting the host root (/:/host_root/) with --privileged is necessary for the disk-space cleanup step, but it significantly widens the blast radius if any step in the job is compromised. Consider scoping the mount to only the directories actually cleaned (/usr/share/dotnet, /usr/local/lib/android, /opt/ghc) or moving the cleanup to a separate pre-job step that doesn't need it, so the main coverage steps run with reduced privileges.


🤖 Generated by Qoder

elif line.startswith("DA:") and current_file:
fields = line[3:].split(",", 2)
if len(fields) >= 2:
current_lines[int(fields[0])] = max(int(fields[1]), 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DA: accumulation logic (current_lines[int(fields[0])] = max(int(fields[1]), 0)) overwrites earlier hits for the same line number within a single record if the same line appears twice. The correct semantics is to accumulate within a record too (matching the cross-record merge done in flush_record). This could under-report hits for pathological but valid LCOV files.

current_lines[int(fields[0])] = current_lines.get(int(fields[0]), 0) + max(int(fields[1]), 0)

🤖 Generated by Qoder

Comment thread tools/coverage/coverage_report.py
Comment thread tools/coverage/gcov_json_isolated.sh
@github-actions github-actions Bot added the ai reviewed AI has reviewed this PR label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Coverage Summary

Scope Covered lines Coverable lines Line coverage
Overall 23960 31428 76.24%
Changed lines 0 0 N/A

Diff base: 7f7f20c2ac037efc53592da321922ff4b22003df
Diff head: HEAD
Changed lines in diff: 0
Changed lines without LCOV data: 0

HTML report and raw LCOV: coverage-report
Workflow run: 30906597308

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai reviewed AI has reviewed this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant